package ace.module.stock.core.impl.manager.impl;

import ace.cmp.core.constants.CoreConstant;
import ace.cmp.core.utils.SystemClockUtils;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.model.SaveContext;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.model.SaveListContext;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.utils.SaveContextUtils;
import ace.cmp.util.AceAssertUtils;
import ace.cmp.util.AceStreamUtils;
import ace.module.common.core.impl.manager.impl.AbstractEntityOfAppManager;
import ace.module.stock.api.enums.WmsStockObjectTypeEnum;
import ace.module.stock.api.model.dto.WmsStockDto;
import ace.module.stock.api.model.input.WmsBizInitInput;
import ace.module.stock.api.model.input.WmsBizInitObjectInput;
import ace.module.stock.api.model.input.WmsBizUniqueIdInput;
import ace.module.stock.core.impl.dao.aggregate.helper.WmsStockAggregateHelper;
import ace.module.stock.core.impl.dao.entity.QWmsStock;
import ace.module.stock.core.impl.dao.entity.WmsApplication;
import ace.module.stock.core.impl.dao.entity.WmsStock;
import ace.module.stock.core.impl.dao.model.bo.WmsStockDetailSaveContextBo;
import ace.module.stock.core.impl.dao.repository.WmsStockRepository;
import ace.module.stock.core.impl.manager.WmsStockManager;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Ops;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.Expressions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * 库存 spring data jdbc querydsl rsql manager implement.
 *
 * @author Caspar
 */
@Slf4j
@AllArgsConstructor
@Component
public class WmsStockManagerImpl
    extends AbstractEntityOfAppManager<WmsStock, Long, WmsStockDto>
    implements WmsStockManager {

  private final WmsStockRepository wmsStockRepository;
  private final WmsStockAggregateHelper wmsStockAggregateHelper;

  @Override
  public WmsStockDetailSaveContextBo assembleInitOrSetSaveListContext(WmsApplication wmsApplication, WmsBizInitInput wmsBizInitInput) {
    Long wmsAppId = wmsApplication.getId();
    List<Long> skuObjectIds = wmsBizInitInput.getObjects()
        .stream()
        .map(WmsBizInitObjectInput::getObjectId)
        .collect(Collectors.toList());
    Long productObjectId = wmsBizInitInput.getObjectParentId();

    List<WmsStock> allOldWmsStocks = this.getProductAndSkuWmsStock(wmsAppId, productObjectId, skuObjectIds);

    List<SaveContext<WmsStock>> wmsSkuStocksSaveContexts = wmsBizInitInput
        .getObjects()
        .stream()
        .map(wmsStockAndSalesQuantityInitInput -> {
          return this.assembleInitOrSetSaveListContext(
              wmsApplication.getId(),
              allOldWmsStocks,
              WmsStockObjectTypeEnum.PRODUCT_SKU,
              wmsBizInitInput.getObjectParentId(),
              wmsStockAndSalesQuantityInitInput.getObjectId(),
              wmsStockAndSalesQuantityInitInput.getQuantity()
          );
        })
        .collect(Collectors.toList());

    Long totalQuantity = wmsSkuStocksSaveContexts.stream()
        .map(SaveContext::getData)
        .map(WmsStock::getQuantity)
        .mapToLong(Long::longValue)
        .sum();

    SaveContext<WmsStock> wmsStockParentSaveContext = this.assembleInitOrSetSaveListContext(
        wmsApplication.getId(),
        allOldWmsStocks,
        WmsStockObjectTypeEnum.PRODUCT,
        CoreConstant.ZERO_LONG,
        wmsBizInitInput.getObjectParentId(),
        totalQuantity
    );

    return WmsStockDetailSaveContextBo.builder()
        .children(SaveContextUtils.of(wmsSkuStocksSaveContexts))
        .stock(wmsStockParentSaveContext)
        .build();
  }

  private List<WmsStock> getProductAndSkuWmsStock(Long wmsAppId, Long productObjectId, List<Long> skuObjectIds) {
    List<WmsStock> wmsStocks = this.wmsStockRepository.query(query -> {
      QWmsStock qWmsStock = QWmsStock.wmsStock;
      return query.select(qWmsStock)
          .from(qWmsStock)
          .where(qWmsStock.wmsAppId.eq(wmsAppId))
          .where(
              Expressions.predicate(
                  Ops.WRAPPED,
                  qWmsStock.objectId.eq(productObjectId),
                  qWmsStock.objectType.eq(WmsStockObjectTypeEnum.PRODUCT.getCode())
              )
          )
          .where(
              Expressions.predicate(
                  Ops.WRAPPED,
                  qWmsStock.objectId.in(skuObjectIds),
                  qWmsStock.objectType.eq(WmsStockObjectTypeEnum.PRODUCT_SKU.getCode())
              )
          )
          .where(qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT))
          .fetch();
    });
    return wmsStocks;
  }

  @Override
  public WmsStockDto findDtoByBizUniqueId(List<WmsStockDto> wmsStockDtos, WmsBizUniqueIdInput bizUniqueIdInput) {
    return AceStreamUtils.ofNullable(wmsStockDtos)
        .filter(p -> Objects.nonNull(p.getObjectId()) && Objects.nonNull(p.getObjectType()))
        .filter(p -> p.getObjectId().longValue() == bizUniqueIdInput.getObjectId().longValue())
        .filter(p -> p.getObjectType().intValue() == bizUniqueIdInput.getObjectType().intValue())
        .findFirst()
        .orElse(null);
  }

  @Transactional(rollbackFor = Throwable.class)
  @Override
  public void initOrSet(WmsApplication wmsApplication, WmsBizInitInput wmsBizInitInput) {
    WmsStockDetailSaveContextBo wmsStockDetailSaveContextBo = this.assembleInitOrSetSaveListContext(wmsApplication, wmsBizInitInput);
    WmsStock wmsStockParent = wmsStockDetailSaveContextBo.getStock().getData();
    List<Long> allIds = wmsStockDetailSaveContextBo.getAllIds();
    if (wmsStockDetailSaveContextBo.getStock().isModify()) {
      this.removeByWmsAppIdAndObjectParentIdAndObjectTypeAndIdNotIn(
          wmsStockParent.getWmsAppId(),
          wmsStockParent.getId(),
          WmsStockObjectTypeEnum.PRODUCT_SKU.getCode(),
          allIds
      );
    }
    SaveListContext<WmsStock> saveListContext = wmsStockDetailSaveContextBo.getSaveListContext();
    if (CollectionUtils.isNotEmpty(saveListContext.getNewOriginData())) {
      this.addIgnore(saveListContext.getNewOriginData(), false);
    }
    if (CollectionUtils.isNotEmpty(saveListContext.getModifyOriginData())) {
      this.modifyIgnore(saveListContext.getModifyOriginData(), false);
    }
  }

  @Override
  public WmsStock getParentStock(Long wmsAppId, Long objectParentId) {
    QWmsStock qWmsStock = QWmsStock.wmsStock;
    Predicate predicate = ExpressionUtils.allOf(
        qWmsStock.wmsAppId.eq(wmsAppId),
        qWmsStock.objectId.eq(objectParentId),
        qWmsStock.objectType.eq(WmsStockObjectTypeEnum.PRODUCT.getCode()),
        qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.wmsStockRepository.findOne(predicate).orElse(null);
  }

  @Override
  public WmsStockDto getParentStockDto(Long wmsAppId, Long objectParentId) {
    WmsStock wmsStock = this.getParentStock(wmsAppId, objectParentId);
    return this.convertToEntityDto(wmsStock);
  }

  @Override
  public List<WmsStock> getChildrenStocks(Long wmsAppId, Long objectParentId) {
    QWmsStock qWmsStock = QWmsStock.wmsStock;
    Predicate predicate = ExpressionUtils.allOf(
        qWmsStock.wmsAppId.eq(wmsAppId),
        qWmsStock.objectParentId.eq(objectParentId),
        qWmsStock.objectType.eq(WmsStockObjectTypeEnum.PRODUCT_SKU.getCode()),
        qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.wmsStockRepository.findAll(predicate);
  }

  @Override
  public List<WmsStockDto> getChildrenStockDtos(Long wmsAppId, Long objectParentId) {
    List<WmsStock> wmsStocks = this.getChildrenStocks(wmsAppId, objectParentId);
    return this.convertToEntityDto(wmsStocks);
  }

  @Override
  public List<WmsStock> getSkuByWmsAppIdAndObjectIdIn(Long wmsAppId, List<Long> objectIds) {
    QWmsStock qWmsStock = QWmsStock.wmsStock;
    Predicate predicate = ExpressionUtils.allOf(
        qWmsStock.wmsAppId.eq(wmsAppId),
        qWmsStock.objectId.in(objectIds),
        qWmsStock.objectType.eq(WmsStockObjectTypeEnum.PRODUCT_SKU.getCode()),
        qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.wmsStockRepository.findAll(predicate);
  }

  @Override
  public List<WmsStockDto> getSkuDtoByWmsAppIdAndObjectIdIn(Long wmsAppId, List<Long> objectIds) {
    List<WmsStock> wmsStocks = this.getSkuByWmsAppIdAndObjectIdIn(wmsAppId, objectIds);
    return this.convertToEntityDto(wmsStocks);
  }

  @Override
  public List<WmsStock> getProductByObjectIdIn(Long wmsAppId, List<Long> objectIds) {
    QWmsStock qWmsStock = QWmsStock.wmsStock;
    Predicate predicate = ExpressionUtils.allOf(
        qWmsStock.wmsAppId.eq(wmsAppId),
        qWmsStock.objectId.in(objectIds),
        qWmsStock.objectType.eq(WmsStockObjectTypeEnum.PRODUCT.getCode()),
        qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT)
    );
    return this.wmsStockRepository.findAll(predicate);
  }

  @Override
  public List<WmsStockDto> getProductDtoByObjectIdIn(Long wmsAppId, List<Long> objectIds) {
    List<WmsStock> wmsStocks = this.getProductByObjectIdIn(wmsAppId, objectIds);
    return this.convertToEntityDto(wmsStocks);
  }

  @Override
  public List<WmsStock> getSkuStockAndAllRecursionParentStocksByWmsAppIdAndSkuWmsStockIdIn(Long wmsAppId, List<Long> skuWmsStockIds) {
    if (CollectionUtils.isEmpty(skuWmsStockIds) || Objects.isNull(wmsAppId)) {
      return new ArrayList<>();
    }
    Integer objectType = WmsStockObjectTypeEnum.PRODUCT_SKU.getCode();

    List<WmsStock> startWmsStocks = this.wmsStockRepository.findByIdInAndWmsAppIdAndObjectTypeAndDeleteFlag(skuWmsStockIds, wmsAppId, objectType, CoreConstant.DELETE_FLAG_NOT);

    return this.getAndConcatAllRecursionParentStocks(startWmsStocks);
  }

  @Override
  public List<WmsStock> getByWmsAppIdAndBizUniqueIdIn(Long wmsAppId, List<WmsBizUniqueIdInput> wmsBizUniqueIdInputs) {

    return this.wmsStockRepository.query(query -> {
      QWmsStock qWmsStock = QWmsStock.wmsStock;

      List<Predicate> objectIdAndObjectTypePredicates = wmsBizUniqueIdInputs.stream()
          .map(wmsBizUniqueIdInput -> {
            return (Predicate) Expressions.predicate(
                Ops.WRAPPED,
                qWmsStock.objectId.eq(wmsBizUniqueIdInput.getObjectId()),
                qWmsStock.objectType.eq(wmsBizUniqueIdInput.getObjectType())
            );
          })
          .collect(Collectors.toList());

      return query.select(qWmsStock)
          .from(qWmsStock)
          .where(qWmsStock.wmsAppId.eq(wmsAppId))
          .where(
              Expressions.predicate(
                  Ops.WRAPPED,
                  ExpressionUtils.anyOf(objectIdAndObjectTypePredicates)
              )
          )
          .where(qWmsStock.deleteFlag.eq(CoreConstant.DELETE_FLAG_NOT))
          .fetch();
    });
  }

  @Override
  public List<WmsStockDto> getDtoByWmsAppIdAndBizUniqueIdIn(Long wmsAppId, List<WmsBizUniqueIdInput> wmsBizUniqueIdInputs) {
    List<WmsStock> wmsStocks = this.getByWmsAppIdAndBizUniqueIdIn(wmsAppId, wmsBizUniqueIdInputs);
    return this.convertToEntityDto(wmsStocks);
  }


  @Override
  public List<WmsStock> getSkuStockAndAllRecursionParentStocksByWmsAppIdAndObjectIdIn(Long wmsAppId, List<Long> objectIds) {
    if (CollectionUtils.isEmpty(objectIds) || Objects.isNull(wmsAppId)) {
      return new ArrayList<>();
    }
    Integer objectType = WmsStockObjectTypeEnum.PRODUCT_SKU.getCode();

    List<WmsStock> startWmsStocks = this.wmsStockRepository.findByWmsAppIdAndObjectIdInAndObjectTypeAndDeleteFlag(wmsAppId, objectIds, objectType, CoreConstant.DELETE_FLAG_NOT);

    return this.getAndConcatAllRecursionParentStocks(startWmsStocks);
  }


  @Override
  public List<WmsStock> getSkuStocksFromList(List<WmsStock> wmsStocks) {
    return wmsStocks.stream()
        .filter(p -> WmsStockObjectTypeEnum.PRODUCT_SKU.getCode().equals(p.getObjectType()))
        .collect(Collectors.toList());
  }

  @Override
  public void checkNotContainsAllWmsStockByObjectId(List<WmsStock> wmsStocks, List<Long> queryObjectIds) {
    AceAssertUtils.isNotNull(wmsStocks.isEmpty(), "sku库存不存在");

    List<Long> existObjectIds = wmsStocks.stream()
        .map(WmsStock::getObjectId)
        .collect(Collectors.toList());

    Long notExistObjectId = queryObjectIds.stream()
        .filter(objectId -> !existObjectIds.contains(objectId))
        .findFirst()
        .orElse(null);

    AceAssertUtils.isNotNull(notExistObjectId, "[%s]sku库存不存在", notExistObjectId);
  }

  @Override
  public List<Long> getIdsFromList(List<WmsStock> wmsStocks) {
    return AceStreamUtils.ofNullable(wmsStocks)
        .map(WmsStock::getId)
        .collect(Collectors.toList());
  }


  private void removeByWmsAppIdAndObjectParentIdAndObjectTypeAndIdNotIn(Long wmsAppId, Long objectParentId, Integer objectType, List<Long> ids) {
    QWmsStock qWmsStock = QWmsStock.wmsStock;
    Predicate predicate = ExpressionUtils.allOf(
        qWmsStock.wmsAppId.eq(wmsAppId),
        qWmsStock.objectParentId.eq(objectParentId),
        qWmsStock.objectType.eq(objectType),
        qWmsStock.id.notIn(ids)
    );
    this.remove(predicate);
  }

  private SaveContext<WmsStock> assembleInitOrSetSaveListContext(
      Long wmsAppId,
      List<WmsStock> allOldWmsStock,
      WmsStockObjectTypeEnum objectTypeEnum,
      Long objectParentId,
      Long objectId,
      Long quantity) {

    WmsStock wmsStock = this.wmsStockAggregateHelper.findByObjectIdAndObjectType(allOldWmsStock, objectId, objectTypeEnum.getCode());
    boolean isAdd = Objects.isNull(wmsStock);
    if (isAdd) {
      wmsStock = WmsStock.builder()
          .id(this.getNewId())
          .wmsAppId(wmsAppId)
          .objectId(objectId)
          .objectParentId(objectParentId)
          .objectType(objectTypeEnum.getCode())

          .deleteFlag(CoreConstant.DELETE_FLAG_NOT)
          .createTime(SystemClockUtils.currentTimeMillis())
          .updateTime(SystemClockUtils.currentTimeMillis())
          .rowVersion(CoreConstant.DEFAULT_ROW_VERSION)
          .build();
    }
    wmsStock.setQuantity(quantity);
    return SaveContextUtils.of(wmsStock, isAdd);
  }

  private Set<Long> getParentIds(Collection<WmsStock> wmsStocks) {
    return wmsStocks.stream()
        .map(WmsStock::getParentId)
        .filter(parentId -> parentId != null && parentId > 0)
        .collect(Collectors.toSet());
  }

  private List<WmsStock> getAndConcatAllRecursionParentStocks(List<WmsStock> startWmsStocks) {
    if (CollectionUtils.isEmpty(startWmsStocks)) {
      return new ArrayList<>();
    }

    List<WmsStock> result = new ArrayList<>(startWmsStocks);

    List<WmsStock> allParentWmsStocks = this.getAllRecursionParentStocks(startWmsStocks);

    result.addAll(allParentWmsStocks);

    return result;
  }

  private List<WmsStock> getAllRecursionParentStocks(List<WmsStock> startWmsStocks) {

    if (CollectionUtils.isEmpty(startWmsStocks)) {
      return new ArrayList<>();
    }

    List<WmsStock> result = new ArrayList<>();

    Set<Long> parentIds = this.getParentIds(startWmsStocks);

    while (CollectionUtils.isNotEmpty(parentIds)) {
      List<WmsStock> parentWmsStocks = this.wmsStockRepository.findByIdInAndDeleteFlagIsNot(parentIds);
      if (CollectionUtils.isNotEmpty(parentWmsStocks)) {
        result.addAll(parentWmsStocks);
      }
      parentIds = this.getParentIds(parentWmsStocks);
    }

    return result;
  }
}
